package com.util.office;

import org.apache.log4j.Logger;

import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import org.artofsolving.jodconverter.OfficeDocumentConverter;
import org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration;
import org.artofsolving.jodconverter.office.OfficeManager;

/**
 * All conversions supported by OpenOffice.org, including<br/>
 * 
 * Microsoft Office to OpenDocument, and viceversa<br/>
 * Word to OpenDocument Text (odt); OpenDocument Text (odt) to Word<br/>
 * Excel to OpenDocument Spreadsheet (ods); OpenDocument Spreadsheet (ods)
 * to Excel<br/>
 * PowerPoint to OpenDocument Presentation (odp); OpenDocument Presentation
 * (odp) to PowerPoint<br/>
 * 
 * Any format to PDF<br/>
 * OpenDocument (Text, Spreadsheet, Presentation) to PDF<br/>
 * Word to PDF; Excel to PDF; PowerPoint to PDF<br/>
 * RTF to PDF; WordPerfect to PDF; ...<br/>
 * And more<br/>
 * 
 * OpenDocument Presentation (odp) to Flash; PowerPoint to Flash<br/>
 * RTF to OpenDocument; WordPerfect to OpenDocument<br/>
 * Any format to HTML (with limitations)<br/>
 * Support for OpenOffice.org 1.0 and old StarOffice formats<br/>
 * 
 * @param source
 * @param destination
 */
public class PDFConverterPool {
	private Logger logger = Logger.getLogger(PDFConverterPool.class);
	private static PDFConverterPool instance = null;
	private Vector<DocumentConverter> pool = null;
	private boolean isDestroying = false;
	
	//Pool Property
	private int maxConn;
	private int minConn;
	private int refreshMinutes;
	
	public static PDFConverterPool getInstance(){
		if (instance==null){
			instance = new PDFConverterPool();
			instance.initPool();
		}
		return instance;
	}
	
	public void initPool(){
		createPool();
	}
	
	/**
	 * Create Pool with initial converter.
	 */
	private void createPool() {
		//TODO get config value from properties
		maxConn = 10;
		minConn = 2;
		refreshMinutes=5;
		
		logger.info("Initial converter pool...");
		pool = new Vector<DocumentConverter>();
		
		//One OfficeManger One Converter
		for (int i=0;i<minConn;i++){
			DocumentConverter docConverter = createOneDocumentConverter();
			pool.add(docConverter);
			logger.info("Add converter pool +1");
		}
		logger.info("Initial converter pool finished with size=" + pool.size());
		Timer timer = new Timer();
		timer.schedule(new PDFConverterPoolTimerTask(), 20000);//check every 20 seconds whether any converter free over the refresh minutes.
	}
	
	private DocumentConverter createOneDocumentConverter() {
		OfficeManager officeManager = initProcessManager();
		OfficeDocumentConverter converter = getNewConverter(officeManager);
		DocumentConverter docConverter = new DocumentConverter();
		docConverter.setManager(officeManager);
		docConverter.setConverter(converter);
		docConverter.setStartTimeInPool(Calendar.getInstance().getTimeInMillis());
		return docConverter;
	}

	/**
	 * Get Converter from Pool.
	 * 
	 * @return OfficeDocumentConverter
	 */
	public synchronized DocumentConverter getDocumentConverter() {
		DocumentConverter docConverter = null;
		if (pool.size() > 0) {
			docConverter = (DocumentConverter) pool.firstElement();
			pool.removeElementAt(0);
			try {
				if (docConverter == null) {
					docConverter = getDocumentConverter();
				}
			} catch (Exception e) {
				docConverter = getDocumentConverter();
			}
		} else if ((maxConn == 0) || (pool.size() < maxConn)) {
			docConverter = createOneDocumentConverter();
		}

		return docConverter;
	}

	/**
	 * Release Converter to Pool.
	 * 
	 * @param docConverter
	 *            DocumentConverter
	 */
	public synchronized void releaseDocumentConverter(DocumentConverter docConverter) {
		try {
			if (docConverter != null) {
				pool.addElement(docConverter);
			}
			notifyAll();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private synchronized void releaseOne() {
		if (pool!=null && pool.firstElement() != null) {
			try {
				pool.removeElementAt(0);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	private OfficeManager initProcessManager(){
		Long begin=Calendar.getInstance().getTimeInMillis();//LOG
		OfficeManager newOfficeManager = new DefaultOfficeManagerConfiguration().buildOfficeManager();
		Long end=Calendar.getInstance().getTimeInMillis();//LOG			
		logger.debug("Build Office Manager Take millisseconds:"+ (end-begin));//LOG
		
		newOfficeManager.start();
		Long end2=Calendar.getInstance().getTimeInMillis();//LOG			
		logger.debug("Office Manager Start sTake millisseconds:"+ (end2-end));//LOG
		return newOfficeManager;
	}
	
	private OfficeDocumentConverter getNewConverter(OfficeManager officeManager){
		Long end1=Calendar.getInstance().getTimeInMillis();//LOG			
	    OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
	    Long end2=Calendar.getInstance().getTimeInMillis();//LOG			
	    logger.debug("New OfficeDocumentConverter Take millisseconds:"+ (end2-end1));//LOG
	    return converter;
	}
	
	private synchronized void destroyProcessManager(OfficeManager officeManager){
		if (officeManager!=null){
			officeManager.stop();
		}else{
			throw new NullPointerException("Office Manager haven't initialize!");
		}
	}
	
	/**
	 * Need to call this destroy method to destroy the Pool.
	 */
	public synchronized void destroyPool(){
		logger.info("Begin to destroy process manager...");
		isDestroying = true;
		if (pool!=null){
			for (DocumentConverter element:pool){
				destroyProcessManager(element.getManager());
			}
		}
		logger.info("Finished to destroy process manager.");
	}
	
	public void resetDocumentConverter(DocumentConverter docConverter){
		logger.info("Reset Document Converter...");
		if (docConverter!=null){
			destroyProcessManager(docConverter.getManager());
			if (pool.size() < maxConn) {
				docConverter = createOneDocumentConverter();
				pool.add(docConverter);
				logger.info("Renew Free Document Converter!");
			}
		}else{
			throw new NullPointerException("Office Manager haven't initialize!");
		}
	}
	
	public void checkPoolSize(){
		logger.info("Checking Pool Size...");
		if (pool.size() > maxConn) {
			releaseOne();
			logger.info("Release one converter. Pool size="+pool.size());
		}
	}
	
	protected Vector<DocumentConverter> getPool() {
		return pool;
	}

	public boolean isDestroying() {
		return isDestroying;
	}

	public void setDestroying(boolean isDestroying) {
		this.isDestroying = isDestroying;
	}

	public int getMaxConn() {
		return maxConn;
	}

	public void setMaxConn(int maxConn) {
		this.maxConn = maxConn;
	}

	public int getMinConn() {
		return minConn;
	}

	public void setMinConn(int minConn) {
		this.minConn = minConn;
	}

	public int getRefreshMinutes() {
		return refreshMinutes;
	}

	public void setRefreshMinutes(int refreshMinutes) {
		this.refreshMinutes = refreshMinutes;
	}
	
}

/**
 * 1. Reset the timeout converter whether any converter free over 5 the refresh minutes. <br>
 * 2. Check the Pool size whether over the max number.
 * @author lican
 *
 */
class PDFConverterPoolTimerTask extends TimerTask{

	@Override
	public void run() {
		if (PDFConverterPool.getInstance().getPool()!=null && !PDFConverterPool.getInstance().isDestroying()){
			for (DocumentConverter element:PDFConverterPool.getInstance().getPool()){
				if (element!=null){
					Calendar cal = Calendar.getInstance();
					cal.setTimeInMillis(element.getStartTimeInPool());
					cal.add(Calendar.MINUTE, PDFConverterPool.getInstance().getRefreshMinutes());
					if (cal.getTimeInMillis()<=Calendar.getInstance().getTimeInMillis()){
						PDFConverterPool.getInstance().resetDocumentConverter(element);
					}
				}
			}
			PDFConverterPool.getInstance().checkPoolSize();
		}
	}
	
}